#include "cpu_info.h"
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>

#if defined(__x86_64__) || defined(__aarch64__) || defined(_WIN64)
#define CPU_FSCANF(fp, cpu)                                                        \
fscanf(fp, "%16s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", ((cpu)->name), &((cpu)->user),        \
    &((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
    &((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
#elif defined(__i386__) || (defined(__arm__) && !defined(__aarch64__))
#define CPU_FSCANF(fp, cpu)                                                        \
fscanf(fp, "%16s %u %u %u %u %u %u %u %u %u %u", ((cpu)->name), &((cpu)->user),        \
    &((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
    &((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
#else
#define CPU_FSCANF(fp, cpu)                                                        \
fscanf(fp, "%16s %u %u %u %u %u %u %u %u %u %u", ((cpu)->name), &((cpu)->user),        \
    &((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
    &((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
#endif
#define CPU_FSCANF_NUM 11


// cpu use rate
static int CpuUsageReadOnce(struct CpuUsageRate *cpu) {
    fflush(cpu->fp);
    if (fseek(cpu->fp, 0, SEEK_SET) != 0) {
        // printf("Seek /proc/stat file error, %s(%d)\n", strerror(errno), errno);
        printf("fseek fail\n");
        return -1;
    }

    for (int i = 0; i < cpu->cpu_num; i++) {
        int ret = CPU_FSCANF(cpu->fp, &cpu->ninfo[i]);
        if (ret != CPU_FSCANF_NUM) {
            printf("CPU_FSCANF ret = %d\n", ret);
            return -1;
        } else {
            #if 0
            printf("%s\t%u\t%u\t%u\t%u\t%u\n", cpu->ninfo[i].name, cpu->ninfo[i].user, cpu->ninfo[i].nice, cpu->ninfo[i].system, cpu->ninfo[i].idle, cpu->ninfo[i].iowait);
            printf("%s\t%u\t%u\t%u\t%u\t%u\n", cpu->ninfo[i].name, cpu->ninfo[i].irq, cpu->ninfo[i].softirq, cpu->ninfo[i].steal, cpu->ninfo[i].guest, cpu->ninfo[i].guest_nice);
            #endif
        }
    }
    return 0;
    
}

int CpuUsageRateInit(struct CpuUsageRate *cpu) {
    if (!cpu) return -1;
    cpu->fp = NULL;
    cpu->cpu_num = 0;
    cpu->ninfo = NULL;
    cpu->cpu_usage_rate = NULL;

    /* 打开全局cpu状态信息虚拟文件 */
    cpu->fp = fopen("/proc/stat", "r");
    if (cpu->fp == NULL) {
        // printf("Open /proc/stat file error, %s(%d)\n", strerror(errno), errno);
        printf("open /proc/stat fail\n");
        return -1;
    }

    #if 1
    // get cpu core
    cpu->cpu_num = sysconf(_SC_NPROCESSORS_ONLN) + 1;
    printf("cpu mum:%d\n", cpu->cpu_num);
    #else
    char buff[4096];
    int ret;
    ret = fread(buff, 1, sizeof(buff), cpu->fp);
    if (ret <= 0) {
        printf("fread fail, ret = %d\n", ret);
        fclose(cpu->fp);
        cpu->fp = NULL;
        return -1;
    }
    char *temp = buff;
    while (1) {
        temp = strstr(temp, "cpu");
        if (temp) {
            cpu->cpu_num++;
            temp+=1;
        } else {
            break;
        }
    }
    #endif
    if (cpu->cpu_num) {
        cpu->ninfo = (struct CpuBaseInfo *)malloc(sizeof(struct CpuBaseInfo) * cpu->cpu_num);
        cpu->cpu_usage_rate = (float*)malloc(sizeof(float) * cpu->cpu_num);
        if (!cpu->ninfo || !cpu->cpu_usage_rate) {
        } else {
            // memset(cpu->ninfo, 0, sizeof(struct CpuBaseInfo) * cpu->cpu_num);
            // memset(cpu->cpu_usage_rate, 0, sizeof(float) * cpu->cpu_num);
            if (CpuUsageReadOnce(cpu) == 0) {
                return 0;
            }
        }
    }

    fclose(cpu->fp);
    free(cpu->ninfo);
    free(cpu->cpu_usage_rate);
    cpu->fp = NULL;
    cpu->ninfo = NULL;
    cpu->cpu_usage_rate = NULL;
    return -1;
}

int GetCpuTemperature(struct CpuUsageRate *cpu) {
    FILE *fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
    if (!fp) {
        // perror("fopen /sys/class/thermal/thermal_zone0/temp");
        return -1;
    }

    char buff[10];
    char *back = fgets(buff, sizeof(buff), fp);
    if (!back) {
        cpu->cpu_temperature = 0;
        fclose(fp);
        return -1;
    } else {
        cpu->cpu_temperature = atoi(buff);
        fclose(fp);
        return 0;
    }
}

int is_file_exists(char *path) {  
    return (access(path, F_OK) == 0);  
} 

int GetCpufrequency(struct CpuUsageRate *cpu) {
    // /sys/devices/system/cpu/cpu0/cpufreq
    char name[256], tmp[128];
    char *max_freq_list[] = {
        "/cpufreq/cpuinfo_max_freq",
        "/cpufreq/scaling_max_freq"
    };
    char *min_freq_list[] = {
        "/cpufreq/cpuinfo_min_freq",
        "/cpufreq/scaling_min_freq"
    };
    char *cur_freq_list[] = {
        "/cpufreq/cpuinfo_cur_freq",
        "/cpufreq/scaling_cur_freq"
    };

    FILE *fp;
    int ret;
    for (int i=1;i<cpu->cpu_num;i++) {
        // max freq
        for (int j=0;j<(sizeof(max_freq_list)/sizeof(max_freq_list[0]));j++) {
            sprintf(name, "%s%s%s","/sys/devices/system/cpu/", cpu->ninfo[i].name, max_freq_list[j]);
            if (is_file_exists(name)) {
                fp = fopen("name", "r");
                if (fp == NULL) {
                    perror("get max_freq fail");
                    return -1;
                }
                if (fread(tmp, 1, 100, fp) <= 0) {
                    perror("read max_freq");
                } else {
                    cpu->ninfo[i].max_freq = atoi(tmp);
                    break;
                }
            } else {
                printf("max_freq not found\n");
            }
        }
        // min freq
        for (int j=0;j<(sizeof(min_freq_list)/sizeof(min_freq_list[0]));j++) {
            sprintf(name, "%s%s%s","/sys/devices/system/cpu/", cpu->ninfo[i].name, min_freq_list[j]);
            if (is_file_exists(name)) {
                fp = fopen("name", "r");
                if (fp == NULL) {
                    perror("get min_freq fail");
                    return -1;
                }
                if (fread(tmp, 1, 100, fp) <= 0) {
                    perror("read min_freq");
                } else {
                    cpu->ninfo[i].min_freq = atoi(tmp);
                    break;
                }
            } else {
                printf("min_freq not found\n");
            }
        }
        // cur freq
        for (int j=0;j<(sizeof(cur_freq_list)/sizeof(cur_freq_list[0]));j++) {
            sprintf(name, "%s%s%s","/sys/devices/system/cpu/", cpu->ninfo[i].name, cur_freq_list[j]);
            if (is_file_exists(name)) {
                fp = fopen("name", "r");
                if (fp == NULL) {
                    perror("get cur_freq fail");
                    return -1;
                }
                if (fread(tmp, 1, 100, fp) <= 0) {
                    perror("read cur_freq");
                } else {
                    cpu->ninfo[i].cur_freq = atoi(tmp);
                    break;
                }
            } else {
                printf("cur_freq not found\n");
            }
        }
    }
    
    return 0;
}

int GetCpuUsageRate(struct CpuUsageRate *cpu) {
    if (!cpu || !cpu->fp) return -1;
    // prepare work
    struct CpuBaseInfo *old_info = (struct CpuBaseInfo *)malloc(sizeof(struct CpuBaseInfo) * cpu->cpu_num);
    if (!old_info) {
        return -1;
    }
    memcpy(old_info, cpu->ninfo, sizeof(struct CpuBaseInfo) * cpu->cpu_num);
    // read new one
    if (CpuUsageReadOnce(cpu) == -1) {
        free(old_info);
        return -1;
    }
    // calculate
    for (int i = 0; i < cpu->cpu_num; i++) {
        CPU_ITEM_SIZE old_used = old_info[i].user + old_info[i].nice + old_info[i].system + old_info[i].irq + old_info[i].softirq + old_info[i].steal + old_info[i].guest + old_info[i].guest_nice;
        CPU_ITEM_SIZE old_all = old_used + old_info[i].idle + old_info[i].iowait;
        CPU_ITEM_SIZE new_used = cpu->ninfo[i].user + cpu->ninfo[i].nice + cpu->ninfo[i].system + cpu->ninfo[i].irq + cpu->ninfo[i].softirq + cpu->ninfo[i].steal + cpu->ninfo[i].guest + cpu->ninfo[i].guest_nice;
        CPU_ITEM_SIZE new_all = new_used + cpu->ninfo[i].idle + cpu->ninfo[i].iowait;
        cpu->cpu_usage_rate[i] = (float)(new_used-old_used)*100/(new_all-old_all);
    }
    free(old_info);
    // temperature
    GetCpuTemperature(cpu);
    return 0;
}

void CpuUsageRateDeinit(struct CpuUsageRate *cpu) {
    if (!cpu) return;
    fclose(cpu->fp);
    free(cpu->ninfo);
    free(cpu->cpu_usage_rate);
}


